/*
 * Loop.cpp
 */

#include "Loop.h"
#include "windows.h"
#include <iostream>
#include "ObjModel.h"


/* First time run only initialization */
Loop::Loop (GL_Window *window)
{
	this->window = window;
	this->keys = &(window->keys);
	this->isRunning = true;
	this->polyMode = false;

	createObjects();

	px = 0.0f;
	py = 0.0f;
	pz = 0.0f;
	rx = 0.0f;
	ry = 0.0f;
	rz = 0.0f;

}

Loop::~Loop ()
{
	//destroyObjects();
}

void Loop::createObjects() {
	skybox = new Skybox();

	/* Timer position is in pixels (0,0)->(800,600), (0,0) is bottom left */
	timer = new Timer();
	timer->setPosition(20.0f, 580.0f, 0.0f);

	level = new Level(20,15);

	player1 = new Player(level, skybox, 0);
	player2 = new Player(level, skybox, 1);
}

void Loop::destroyObjects() {
	delete timer;
	delete skybox;
	delete player1;
	delete player2;
	delete level;
	delete[] bonuses;
}

/* The BIG GAME LOOP */
void Loop::run() {
	std::cout << "Create window: " << std::endl;
	if (window->createWindowGL() == false) {
		return;
	}

	std::cout << "Setup view: " << std::endl;
	if (setupView() == false) {
		return;
	}

	std::cout << "Initialize: " << std::endl;
	if (initialize() == false) {
		//TODO print failure message
		return;
	}

	/* MAIN LOOP */
	std::cout << "Loop: " << std::endl;
	while (isRunning) {
		window->processMessages();


		draw ();										// Draw Our Scene
		update (timer->getTime());

		window->swapBuffers ();							// Swap Buffers (Double Buffering)
		//Sleep(10); // uncomment to reduce CPU load (enable upper boundary of FPS)
	}
}

/* Key processing and logic update - takes miliseconds since last frame as parameter
 * Toggles are complicated, because the keys->keyDown[] is true, until released and if pressed,
 * it gets reset to true on an interval
 *
 * Toggles that recreate the window are slow, so they can be simple
 */
void Loop::update (float milis)
{
	/* This flag goes up on closing the window */
	if (window->terminate) {
		isRunning = false;
		return;
	}

	if (keys->keyDown [VK_ESCAPE] == true) {
		isRunning = false;
		return;
	}

	if(scale < 0.5f) {
		change = -change;
		scale = 0.5001f;
	}
	if(scale > 1.5f) {
		change=-change;
		scale = 1.4999f;
	}
	scale+=change*milis;

	//checking player bonus states
	if(player1->isDrunk()){
		player1->drunk_countdown -= milis;
		if(player1->drunk_countdown < 0){
			player1->drunk_countdown = 10000.0;
			player1->setSober();
		}
	}

	if(player2->isDrunk()){
		player2->drunk_countdown -= milis;
		if(player2->drunk_countdown < 0){
			player2->drunk_countdown = 10000.0;
			player2->setSober();
		}
	}

	if(player1->isOnSkates()){
		player1->skates_countdown -= milis;
		if(player1->skates_countdown < 0){
			player1->skates_countdown = 10000.0;
			player1->takeAwaySkates();
		}
	}

	if(player2->isOnSkates()){
		player2->skates_countdown -= milis;
		if(player2->skates_countdown < 0){
			player2->skates_countdown = 10000.0;
			player2->takeAwaySkates();
		}
	}


	if (player1->isDead() || player2->isDead()) {
		player1->game_over = true;
		player2->game_over = true;
	}


	/* Fullscreen toggle */
	if (keys->keyDown [VK_F1] == true)
	{
		window->toggleFullScreen();
		setupView();
		initialize();
	}

	/* Windows multisampling toggle */
	if (keys->keyDown [VK_F2] == true)						// Is F1 Being Pressed?
	{
		window->toggleMultisample();
		setupView();
		initialize();
	}

	/* Polygon mode: fill or line */
	if (keys->keyDown [VK_F3] == true) {
		if (keys->keyNoticed [VK_F3] == false) {
			keys->keyNoticed [VK_F3] = true;
			if (polyMode)
				polyMode = false;
			else
				polyMode = true;
		}
	}
	else
		keys->keyNoticed [VK_F3] = false;


	/* Player movement. Copy paste if we have more players, can use the same variables */
	px = py = pz = rx = ry = rz = 0.0f;

	if (keys->keyDown [VK_LEFT] == true){
		if(player1->isDrunk())
			rz -= 1;
		else
			rz += 1;
	}

	if (keys->keyDown [VK_RIGHT] == true){
		if(player1->isDrunk())
			rz += 1;
		else
			rz -= 1;
	}

	if (keys->keyDown [VK_UP] == true){
		if(player1->isDrunk())
			py -= 1;
		else
			py += 1;
	}

	if (keys->keyDown [VK_DOWN] == true){
		if(player1->isDrunk())
			py += 1;
		else
			py -= 1;
	}

	if (keys->keyDown [(UCHAR)'K'] == true){
		if(player1->isDrunk())
			rx -= 1;
		else
			rx += 1;
	}

	if (keys->keyDown [(UCHAR)'I'] == true){
		if(player1->isDrunk())
			rx += 1;
		else
			rx -= 1;
	}

	if (keys->keyDown [(UCHAR)'J'] == true){
		if(player1->isDrunk())
			px += 1;
		else
			px -= 1;
	}

	if (keys->keyDown [(UCHAR)'L'] == true){
		if(player1->isDrunk())
			px -= 1;
		else
			px += 1;
	}

	// place a bomb when the player1 presses Ctrl
	if (keys->keyDown [VK_CONTROL ] == true) {
		if (keys->keyNoticed [VK_CONTROL ] == false) {
			keys->keyNoticed [VK_CONTROL ] = true;
			player1->setBomb();
		}
	}
	else
		keys->keyNoticed [VK_CONTROL] = false;

//	if(player1->isBlocked())
//		skybox->rotateSkybox(true, rx, 0, milis);
//	else
//		skybox->rotateSkybox(true, rx, rz, milis);
	player1->movePlayer(px, py, 0, milis);
	player1->rotatePlayer(rx, rz, milis);

	px = py = pz = rx = ry = rz = 0.0f;

	if (keys->keyDown [(UCHAR)'F'] == true){
		if(player2->isDrunk())
			rz -= 1;
		else
			rz += 1;
	}

	if (keys->keyDown [(UCHAR)'H'] == true){
		if(player2->isDrunk())
			rz += 1;
		else
			rz -= 1;
	}

	if (keys->keyDown [(UCHAR)'T'] == true){
		if(player2->isDrunk())
			py -= 1;
		else
			py += 1;
	}

	if (keys->keyDown [(UCHAR)'G'] == true){
		if(player2->isDrunk())
			py += 1;
		else
			py -= 1;
	}

	if (keys->keyDown [(UCHAR)'S'] == true){
		if(player2->isDrunk())
			rx -= 1;
		else
			rx += 1;
	}

	if (keys->keyDown [(UCHAR)'W'] == true){
		if(player2->isDrunk())
			rx += 1;
		else
			rx -= 1;
	}

	if (keys->keyDown [(UCHAR)'A'] == true){
		if(player2->isDrunk())
			px += 1;
		else
			px -= 1;
	}

	if (keys->keyDown [(UCHAR)'D'] == true){
		if(player2->isDrunk())
			px -= 1;
		else
			px += 1;
	}

	// place a bomb when the player2 presses X
	if (keys->keyDown [(UCHAR)'X'] == true) {
		if (keys->keyNoticed [(UCHAR)'X'] == false) {
			keys->keyNoticed [(UCHAR)'X'] = true;
			player2->setBomb(); // size of explosion == 2
		}
	}
	else
		keys->keyNoticed [(UCHAR)'X'] = false;

//	if(player2->isBlocked())
//		skybox->rotateSkybox(false, rx, 0, milis);
//	else
//		skybox->rotateSkybox(false, rx, rz, milis);
	player2->movePlayer(px, py, 0, milis);
	player2->rotatePlayer(rx, rz, milis);

	level->update(milis);
}

/* Class and variable initialization
 * needs to be called on every window create (fullscreen and ARB toggle),
 * to rebuild textures and fonts
 */
bool Loop::initialize()
{
	skybox->initializeGL();
	player1->initializeGL();
	player2->initializeGL();
	timer->initializeGL();
	level->initializeGL();

	bonuses[0] = new ObjModel("data/bomb1.obj");
	bonuses[1] = new ObjModel("data/bonus_fire.obj");
	bonuses[2] = new ObjModel("data/bonus_vodka.obj");
	bonuses[3] = new ObjModel("data/bonus_kotalke.obj");
	scale = 1.0;
	change = -0.003;

	Model3D::buildTexture("data/red_win.bmp", red_win);
	Model3D::buildTexture("data/blue_win.bmp", blue_win);

	buildFont();

	return true;
}

/* GL initialization */
bool Loop::setupView()
{
	glViewport(0 , 0,window->width ,window->height);			// Set Up A Viewport
	glMatrixMode(GL_PROJECTION);								// Select The Projection Matrix
	glLoadIdentity();											// Reset The Projection Matrix
	gluPerspective(50, (float)window->width/(float)window->height, 0.1f,  2000); // Set Our Perspective
	glMatrixMode(GL_MODELVIEW);									// Select The Modelview Matrix
	glLoadIdentity();											// Reset The Modelview Matrix

	GLfloat LightAmbient1[]=		{ 0.8f, 0.8f, 0.8f, 1.0f };
	GLfloat LightDiffuse1[]=		{ 1.0f, 1.0f, 0.9f, 1.0f };
	GLfloat LightPosition1[]=	{ 0.0f, 0.0f, 2.0f, 1.0f };
	glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient1);				// Setup The Ambient Light
	glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse1);				// Setup The Diffuse Light
	glLightfv(GL_LIGHT1, GL_POSITION,LightPosition1);			// Position The Light
	glEnable(GL_LIGHT1);										// Enable Light One

	GLfloat LightAmbient2[]=		{ 0.8f, 0.8f, 0.8f, 1.0f };
	//GLfloat LightDiffuse2[]=		{ 1.0f, 1.0f, 0.9f, 1.0f };
	GLfloat LightPosition2[]=	{ 0.0f, 0.0f, 2.0f, 1.0f };
	glLightfv(GL_LIGHT2, GL_AMBIENT, LightAmbient2);				// Setup The Ambient Light
	//glLightfv(GL_LIGHT2, GL_DIFFUSE, LightDiffuse2);				// Setup The Diffuse Light
	glLightfv(GL_LIGHT2, GL_POSITION,LightPosition2);			// Position The Light
	glEnable(GL_LIGHT2);										// Enable Light Two

	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);						// Set The Clear Color To Black
	glClearDepth(1.0);											// Enables Clearing Of The Depth Buffer
	glDepthFunc(GL_LEQUAL);										// The Type Of Depth Test To Do
	glBlendFunc(GL_SRC_ALPHA,GL_ONE);							// Select The Type Of Blending
	glShadeModel(GL_SMOOTH);									// Select Smooth Shading

	glEnable(GL_DEPTH_TEST);									// Enable Depth Testing
	glEnable(GL_LIGHTING);
	glEnable(GL_TEXTURE_2D);
	//glEnable(GL_COLOR_MATERIAL);

	return true;

}

/* Draw everything, go through all the classes */
void Loop::draw (void)
{
	glEnable(GL_MULTISAMPLE_ARB);								// Enable Our Multisampling

	if (polyMode)
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	else
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);			// Clear Screen And Depth Buffer
	glLoadIdentity();												// Reset The View


	for (int view = 0; view < 2; view++) {
		if (view == 0) {
			glViewport(0 , 0,window->width/2 - 5, window->height);			// Set Up A Viewport
			glMatrixMode(GL_PROJECTION);								// Select The Projection Matrix
			glLoadIdentity();											// Reset The Projection Matrix
			gluPerspective(50, ((float)window->width - 5)/2.0/(float)window->height, 0.1f,  2000); // Set Our Perspective
			glMatrixMode(GL_MODELVIEW);									// Select The Modelview Matrix
			glLoadIdentity();											// Reset The Modelview Matrix
		}
		else if (view == 1) {
			glViewport(window->width/2 + 5 , 0,window->width/2 -5, window->height);			// Set Up A Viewport
			glMatrixMode(GL_PROJECTION);								// Select The Projection Matrix
			glLoadIdentity();											// Reset The Projection Matrix
			gluPerspective(50, ((float)window->width -5)/2.0/(float)window->height, 0.1f,  2000); // Set Our Perspective
			glMatrixMode(GL_MODELVIEW);									// Select The Modelview Matrix
			glLoadIdentity();
		}

		GLfloat LightPosition1[]=	{ 0.0f, 3.0f, 3.0f, 1.0f };
		glLightfv(GL_LIGHT1, GL_POSITION,LightPosition1);

		glEnable(GL_TEXTURE_2D);

		skybox->renderGL(view == 0);

		/* Also draws the player */
		if (view == 0) {
			player1->setCameraMatrix();
			player2->renderGL();
		}
		else if (view == 1) {
			player2->setCameraMatrix();
			player1->renderGL();
		}

		GLfloat LightPosition2[]=	{ 10.0f, 10.0f, 10.0f, 1.0f };
		glLightfv(GL_LIGHT2, GL_POSITION,LightPosition2);

		// if location of destructable box 'is on fire' -> destroy it
		level->render3D();
		for(int i=0; i<level->cols; i++){
			for(int j=0; j<level->rows; j++){
				if(level->model[i][j].isOnFire)
					level->destroyBox(i, j);

				// drawing bonus
				if(level->model[i][j].bonus_type > 0){
					glPushMatrix();
					Vertex v(0.49+level->model[i][j].x, 0.5+level->model[i][j].y, 0);
					level->heightCorrection(v);
					glTranslatef(v.x, v.y, v.z +0.25);
					glScalef(scale, scale, scale);

					//glEnable(GL_BLEND);		// Turn Blending On
					bonuses[level->model[i][j].bonus_type-1]->renderGL();
					//glDisable(GL_BLEND);		// Turn Blending Off
					glPopMatrix();
				}

				// picking up bonus
				int x,y,z;
				player1->getPosition(x,y,z);
				if( x == (int)(level->model[i][j].x)  &&  y == (int)(level->model[i][j].y) ){
					if(level->model[x][y].bonus_type > 0){
						player1->pickupBonus(level->model[x][y].bonus_type);
						level->model[i][j].bonus_type = 0;
					}
				}
				player2->getPosition(x,y,z);
				if( x == (int)(level->model[i][j].x)  &&  y == (int)(level->model[i][j].y) ){
					if(level->model[x][y].bonus_type > 0){
						player2->pickupBonus(level->model[x][y].bonus_type);
						level->model[i][j].bonus_type = 0;
					}
				}

			}
		}
		// set all level locations 'not on fire' so that we can then recheck during 'renderBombs'
		for(int i=0; i<level->cols; i++)
			for(int j=0; j<level->rows; j++)
				level->model[i][j].isOnFire = false;
		player1->renderBombs();
		player2->renderBombs();

		//glEnable(GL_BLEND);
		//glEnable(GL_TEXTURE_2D);

		glColor3f(1.0f,0.0f,0.0f);
		if (view == 0) {
			if(player1->isDrunk())
				glPrint(250, 300, "GET ON REHAB, DUDE...");
			if(player1->isOnSkates())
				glPrint(250, 400, "NICE WHEELS, MAN!");
			glPrint(50, 50, "Bombs: %d", player1->numBombs());
			glPrint(50, 30, "Power: %d", player1->numFlame());
			if (player1->numLives() <= 0)
				glPrint(50, 10, "Lives: dude, you are dead!");
			else
				glPrint(50, 10, "Lives: %d", player1->numLives());
		}
		else if (view == 1) {
			if(player2->isDrunk())
				glPrint(250, 300, "GET ON REHAB, DUDE...");
			if(player2->isOnSkates())
				glPrint(250, 400, "NICE WHEELS, MAN!");
			glPrint(50, 50, "Bombs: %d", player2->numBombs());
			glPrint(50, 30, "Power: %d", player2->numFlame());
			if (player2->numLives() <= 0)
				glPrint(50, 10, "Lives: dude, you are dead!");
			else
				glPrint(50, 10, "Lives: %d", player2->numLives());
		}

	}

	glViewport(0 , 0,window->width, window->height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(50, (float)window->width/(float)window->height, 0.1f,  2000);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();


	if (player1->isDead() || player2->isDead()) {
		glClear (GL_DEPTH_BUFFER_BIT);
		glEnable(GL_TEXTURE_2D);

		if (player1->isDead())
			glBindTexture(GL_TEXTURE_2D, blue_win);
		else
			glBindTexture(GL_TEXTURE_2D, red_win);

		glBegin(GL_QUADS);
			glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  -4.0f);
			glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  -4.0f);
			glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  -4.0f);
			glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  -4.0f);
		glEnd();
	}
	/* Display FPS*/
	//timer->renderGL();
}

